// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include <QDebug>
#include <QDir>
#include <QGuiApplication>
#include <QStringList>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextDocument>
#include <QBuffer>

#include <cplusplus/Control.h>
#include <cplusplus/Parser.h>
#include <cplusplus/AST.h>
#include <cplusplus/ASTVisitor.h>
#include <cplusplus/Symbols.h>
#include <cplusplus/CoreTypes.h>
#include <cplusplus/Literals.h>
#include <cplusplus/CppDocument.h>
#include <cplusplus/Overview.h>
#include <cplusplus/Names.h>
#include <cplusplus/Scope.h>
#include <cplusplus/BackwardsScanner.h>

#include <utils/algorithm.h>
#include <utils/changeset.h>

#include <iostream>
#include <cstdlib>

using namespace CPlusPlus;

static const char copyrightHeader[] =
"// Copyright (c) 2008 Roberto Raggi <roberto.raggi@gmail.com>\n"
"//\n"
"// Permission is hereby granted, free of charge, to any person obtaining a copy\n"
"// of this software and associated documentation files (the \"Software\"), to deal\n"
"// in the Software without restriction, including without limitation the rights\n"
"// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
"// copies of the Software, and to permit persons to whom the Software is\n"
"// furnished to do so, subject to the following conditions:\n"
"//\n"
"// The above copyright notice and this permission notice shall be included in\n"
"// all copies or substantial portions of the Software.\n"
"//\n"
"// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
"// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
"// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
"// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
"// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
"// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
"// THE SOFTWARE.\n"
;

static const char generatedHeader[] =
"\n"
"//\n"
"//  W A R N I N G\n"
"//  -------------\n"
"//\n"
"// This file is automatically generated by \"cplusplus-update-frontend\".\n"
"// Changes will be lost.\n"
"//\n"
"\n"
;

static QIODevice::OpenMode openFlags = QIODevice::WriteOnly | QIODevice::Text;

static void closeBufferAndWriteIfChanged(QBuffer& textBuffer, const QString& filePath)
{
    textBuffer.close();

    std::string filePathForLog = QDir::toNativeSeparators(filePath).toLatin1().toStdString();

    QFile file(filePath);
    if (! file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        std::cerr << "Cannot open " << filePathForLog << std::endl;
        return;
    }
    QTextStream fileReader(&file);
    QBuffer currentTextBuffer;
    currentTextBuffer.open(QIODevice::WriteOnly | QIODevice::Text);
    QTextStream currentTextBufferWriter(&currentTextBuffer);
    while (!fileReader.atEnd()) {
        currentTextBufferWriter << fileReader.readLine() << Qt::endl;
    }
    file.close();
    currentTextBuffer.close();

    if (currentTextBuffer.data() != textBuffer.data()) {
        if (!file.open(QIODevice::WriteOnly)) {
            std::cerr << "Cannot open " << filePathForLog << std::endl;
            return;
        }
        file.write(textBuffer.data());
        file.close();

        std::cout << "changed: " << filePathForLog << std::endl;
    } else {
        std::cout << "not changed: " << filePathForLog << std::endl;
    }
}

static void writeIfChanged(const QString& string, const QString& filePath)
{
    QBuffer buffer;
    buffer.open(openFlags);
    QTextStream output(&buffer);
    output << string;
    closeBufferAndWriteIfChanged(buffer, filePath);
}


class ASTNodes
{
public:
    ClassSpecifierAST *base = nullptr; // points to "class AST"
    QList<ClassSpecifierAST *> deriveds; // n where n extends AST
    QList<QTextCursor> endOfPublicClassSpecifiers;
};

static Document::Ptr AST_h_document;
static ASTNodes astNodes;

static QTextCursor createCursor(TranslationUnit *unit, AST *ast, QTextDocument *document)
{
    int startLine, startColumn, endLine, endColumn;
    unit->getTokenPosition(ast->firstToken(), &startLine, &startColumn);
    unit->getTokenEndPosition(ast->lastToken() - 1, &endLine, &endColumn);

    QTextCursor tc(document);
    tc.setPosition(document->findBlockByNumber(startLine - 1).position());
    tc.setPosition(document->findBlockByNumber(endLine - 1).position() + endColumn - 1,
                   QTextCursor::KeepAnchor);

    int charsToSkip = 0;
    forever {
        QChar ch = document->characterAt(tc.position() + charsToSkip);

        if (! ch.isSpace())
            break;

        ++charsToSkip;

        if (ch == QChar::ParagraphSeparator)
            break;
    }

    tc.setPosition(tc.position() + charsToSkip, QTextCursor::KeepAnchor);
    return tc;
}

class FindASTNodes: protected ASTVisitor
{
public:
    FindASTNodes(Document::Ptr doc, QTextDocument *document)
        : ASTVisitor(doc->translationUnit()), document(document)
    {
    }

    ASTNodes operator()(AST *ast)
    {
        accept(ast);
        return _nodes;
    }

protected:
    virtual bool visit(ClassSpecifierAST *ast)
    {
        Class *klass = ast->symbol;
        Q_ASSERT(klass != nullptr);

        const QString className = oo.prettyName(klass->name());

        if (className.endsWith(QLatin1String("AST"))) {
            if (className == QLatin1String("AST"))
                _nodes.base = ast;
            else {
                _nodes.deriveds.append(ast);

                AccessDeclarationAST *accessDeclaration = nullptr;
                for (DeclarationListAST *it = ast->member_specifier_list; it; it = it->next) {
                    if (AccessDeclarationAST *decl = it->value->asAccessDeclaration()) {
                        if (tokenKind(decl->access_specifier_token) == T_PUBLIC)
                            accessDeclaration = decl;
                    }
                }

                if (! accessDeclaration)
                    qDebug() << "no access declaration for class:" << className;

                Q_ASSERT(accessDeclaration != nullptr);

                QTextCursor tc = createCursor(translationUnit(), accessDeclaration, document);
                tc.setPosition(tc.position());

                _nodes.endOfPublicClassSpecifiers.append(tc);
            }
        }

        return true;
    }

private:
    QTextDocument *document;
    ASTNodes _nodes;
    Overview oo;
};

class Accept0CG: protected ASTVisitor
{
    QDir _cplusplusDir;
    QTextStream *out = nullptr;

public:
    Accept0CG(const QDir &cplusplusDir, TranslationUnit *unit)
        : ASTVisitor(unit), _cplusplusDir(cplusplusDir)
    { }

    QList<QByteArray> classes() const { return classMap.keys(); }

    void operator()(AST *ast)
    {
        QFileInfo fileInfo(_cplusplusDir, QLatin1String("ASTVisit.cpp"));

        QBuffer buffer;
        buffer.open(openFlags);
        QTextStream output(&buffer);
        out = &output;

        *out << copyrightHeader << generatedHeader
             << "\n"
                "#include \"AST.h\"\n"
                "#include \"ASTVisitor.h\"\n"
                "\n"
                "using namespace CPlusPlus;\n"
             << Qt::endl;

        accept(ast);

        closeBufferAndWriteIfChanged(buffer, fileInfo.absoluteFilePath());
    }

protected:
    using ASTVisitor::visit;

    QMap<QByteArray, ClassSpecifierAST *> classMap;

    QByteArray id_cast(NameAST *name)
    {
        if (! name)
            return QByteArray();

        const Identifier *id = identifier(name->asSimpleName()->identifier_token);

        return QByteArray::fromRawData(id->chars(), id->size());
    }

    void visitMembers(Class *klass)
    {
        // *out << "        // visit " << className.constData() << Qt::endl;
        for (int i = 0; i < klass->memberCount(); ++i) {
            Symbol *member = klass->memberAt(i);
            if (! member->name())
                continue;

            const Identifier *id = member->name()->identifier();

            if (! id)
                continue;

            const QByteArray memberName = QByteArray::fromRawData(id->chars(), id->size());
            if (member->type()->asIntegerType() && memberName.endsWith("_token")) {
                // nothing to do. The member is a token.

            } else if (PointerType *ptrTy = member->type()->asPointerType()) {

                if (NamedType *namedTy = ptrTy->elementType()->asNamedType()) {
                    QByteArray typeName = namedTy->name()->identifier()->chars();

                    if (typeName.endsWith("AST") && memberName != "next")
                        *out << "        accept(" << memberName.constData() << ", visitor);"
                             << Qt::endl;
                }
            }
        }

        for (int i = 0; i < klass->baseClassCount(); ++i) {
            const QByteArray baseClassName = klass->baseClassAt(i)->identifier()->chars();

            if (ClassSpecifierAST *baseClassSpec = classMap.value(baseClassName, nullptr))
                visitMembers(baseClassSpec->symbol);
        }
    }

    bool checkMethod(Symbol *accept0Method) const
    {
        Declaration *decl = accept0Method->asDeclaration();
        if (! decl)
            return false;

        Function *funTy = decl->type()->asFunctionType();
        if (! funTy)
            return false;

        else if (funTy->isPureVirtual())
            return false;

        return true;
    }

    virtual bool visit(ClassSpecifierAST *ast)
    {
        Class *klass = ast->symbol;
        const QByteArray className = id_cast(ast->name);

        const Identifier *visit_id = control()->identifier("accept0");
        Symbol *accept0Method = klass->find(visit_id);
        for (; accept0Method; accept0Method = accept0Method->next()) {
            if (accept0Method->identifier() != visit_id)
                continue;

            if (checkMethod(accept0Method))
                break;
        }

        if (! accept0Method)
            return true;

        classMap.insert(className, ast);

        *out << "void " << className.constData() << "::accept0(ASTVisitor *visitor)" << Qt::endl
             << "{" << Qt::endl
             << "    if (visitor->visit(this)) {" << Qt::endl;

        visitMembers(klass);

        *out << "    }" << Qt::endl
             << "    visitor->endVisit(this);" << Qt::endl
             << "}" << Qt::endl
             << Qt::endl;

        return true;
    }
};

class Match0CG: protected ASTVisitor
{
    QDir _cplusplusDir;
    QTextStream *out = nullptr;

public:
    Match0CG(const QDir &cplusplusDir, TranslationUnit *unit)
        : ASTVisitor(unit), _cplusplusDir(cplusplusDir)
    { }

    void operator()(AST *ast)
    {
        QFileInfo fileInfo(_cplusplusDir, QLatin1String("ASTMatch0.cpp"));

        QBuffer buffer;
        buffer.open(openFlags);
        QTextStream output(&buffer);
        out = &output;
        *out << copyrightHeader << generatedHeader
             << "\n"
                "#include \"AST.h\"\n"
                "#include \"ASTMatcher.h\"\n"
                "\n"
                "using namespace CPlusPlus;\n"
             << Qt::endl;

        accept(ast);

        closeBufferAndWriteIfChanged(buffer, fileInfo.absoluteFilePath());
    }

protected:
    using ASTVisitor::visit;

    QMap<QByteArray, ClassSpecifierAST *> classMap;

    QByteArray id_cast(NameAST *name)
    {
        if (! name)
            return QByteArray();

        const Identifier *id = identifier(name->asSimpleName()->identifier_token);

        return QByteArray::fromRawData(id->chars(), id->size());
    }

    void visitMembers(Class *klass)
    {
        Overview oo;
        const QString className = oo.prettyName(klass->name());

        *out << "    if (" << className << " *_other = pattern->as"
             << className.left(className.length() - 3) << "())" << Qt::endl;

        *out << "        return matcher->match(this, _other);" << Qt::endl;

        *out << Qt::endl;
    }

    bool checkMethod(Symbol *accept0Method) const
    {
        Declaration *decl = accept0Method->asDeclaration();
        if (! decl)
            return false;

        Function *funTy = decl->type()->asFunctionType();
        if (! funTy)
            return false;

        else if (funTy->isPureVirtual())
            return false;

        return true;
    }

    virtual bool visit(ClassSpecifierAST *ast)
    {
        Class *klass = ast->symbol;
        const QByteArray className = id_cast(ast->name);

        const Identifier *match0_id = control()->identifier("match0");
        Symbol *accept0Method = klass->find(match0_id);
        for (; accept0Method; accept0Method = accept0Method->next()) {
            if (accept0Method->identifier() != match0_id)
                continue;

            if (checkMethod(accept0Method))
                break;
        }

        if (! accept0Method)
            return true;

        classMap.insert(className, ast);

        *out << "bool " << className.constData() << "::match0(AST *pattern, ASTMatcher *matcher)"
             << Qt::endl
             << "{" << Qt::endl;

        visitMembers(klass);

        *out << "    return false;" << Qt::endl << "}" << Qt::endl << Qt::endl;

        return true;
    }
};


class MatcherCPPCG: protected ASTVisitor
{
    QDir _cplusplusDir;
    QTextStream *out = nullptr;

public:
    MatcherCPPCG(const QDir &cplusplusDir, TranslationUnit *unit)
        : ASTVisitor(unit), _cplusplusDir(cplusplusDir)
    { }

    void operator()(AST *ast)
    {
        QFileInfo fileInfo(_cplusplusDir, QLatin1String("ASTMatcher.cpp"));

        QBuffer buffer;
        buffer.open(openFlags);
        QTextStream output(&buffer);
        out = &output;

        *out << copyrightHeader << Qt::endl
             << generatedHeader << "#include \"AST.h\"" << Qt::endl
             << "#include \"ASTMatcher.h\"" << Qt::endl
             << Qt::endl
             << "using namespace CPlusPlus;" << Qt::endl
             << Qt::endl
             << "ASTMatcher::ASTMatcher()" << Qt::endl
             << "{ }" << Qt::endl
             << Qt::endl
             << "ASTMatcher::~ASTMatcher()" << Qt::endl
             << "{ }" << Qt::endl
             << Qt::endl;

        accept(ast);

        closeBufferAndWriteIfChanged(buffer, fileInfo.absoluteFilePath());
    }

protected:
    using ASTVisitor::visit;

    QMap<QByteArray, ClassSpecifierAST *> classMap;

    QByteArray id_cast(NameAST *name)
    {
        if (! name)
            return QByteArray();

        const Identifier *id = identifier(name->asSimpleName()->identifier_token);

        return QByteArray::fromRawData(id->chars(), id->size());
    }

    void visitMembers(Class *klass)
    {
        for (int i = 0; i < klass->memberCount(); ++i) {
            Symbol *member = klass->memberAt(i);
            if (! member->name())
                continue;

            const Identifier *id = member->name()->identifier();

            if (! id)
                continue;

            const QByteArray memberName = QByteArray::fromRawData(id->chars(), id->size());
            if (member->type()->asIntegerType() && memberName.endsWith("_token")) {
                *out << "    pattern->" << memberName << " = node->" << memberName << ";" << Qt::endl
                     << Qt::endl;

            } else if (PointerType *ptrTy = member->type()->asPointerType()) {

                if (NamedType *namedTy = ptrTy->elementType()->asNamedType()) {
                    QByteArray typeName = namedTy->name()->identifier()->chars();

                    if (typeName.endsWith("AST")) {
                        *out << "    if (! pattern->" << memberName << ")" << Qt::endl
                             << "        pattern->" << memberName << " = node->" << memberName
                             << ";" << Qt::endl
                             << "    else if (! AST::match(node->" << memberName << ", pattern->"
                             << memberName << ", this))" << Qt::endl
                             << "        return false;" << Qt::endl
                             << Qt::endl;
                    }
                }
            }
        }

        for (int i = 0; i < klass->baseClassCount(); ++i) {
            const QByteArray baseClassName = klass->baseClassAt(i)->identifier()->chars();

            if (ClassSpecifierAST *baseClassSpec = classMap.value(baseClassName, nullptr))
                visitMembers(baseClassSpec->symbol);
        }
    }

    bool checkMethod(Symbol *accept0Method) const
    {
        Declaration *decl = accept0Method->asDeclaration();
        if (! decl)
            return false;

        Function *funTy = decl->type()->asFunctionType();
        if (! funTy)
            return false;

        else if (funTy->isPureVirtual())
            return false;

        return true;
    }

    virtual bool visit(ClassSpecifierAST *ast)
    {
        Class *klass = ast->symbol;
        const QByteArray className = id_cast(ast->name);

        const Identifier *match0_id = control()->identifier("match0");
        Symbol *match0Method = klass->find(match0_id);
        for (; match0Method; match0Method = match0Method->next()) {
            if (match0Method->identifier() != match0_id)
                continue;

            if (checkMethod(match0Method))
                break;
        }

        if (! match0Method)
            return true;

        classMap.insert(className, ast);

        *out << "bool ASTMatcher::match(" << className.constData() << " *node, "
             << className.constData() << " *pattern)" << Qt::endl
             << "{" << Qt::endl
             << "    (void) node;" << Qt::endl
             << "    (void) pattern;" << Qt::endl
             << Qt::endl;

        visitMembers(klass);

        *out << "    return true;" << Qt::endl << "}" << Qt::endl << Qt::endl;

        return true;
    }
};

class CloneCPPCG: protected ASTVisitor
{
    QDir _cplusplusDir;
    QTextStream *out = nullptr;

public:
    CloneCPPCG(const QDir &cplusplusDir, TranslationUnit *unit)
        : ASTVisitor(unit), _cplusplusDir(cplusplusDir)
    { }

    void operator()(AST *ast)
    {
        QFileInfo fileInfo(_cplusplusDir, QLatin1String("ASTClone.cpp"));

        QBuffer buffer;
        buffer.open(openFlags);
        QTextStream output(&buffer);
        out = &output;

        *out << copyrightHeader << generatedHeader << "#include \"AST.h\"" << Qt::endl
             << "#include \"MemoryPool.h\"" << Qt::endl
             << Qt::endl
             << "using namespace CPlusPlus;" << Qt::endl
             << Qt::endl;

        accept(ast);

        closeBufferAndWriteIfChanged(buffer, fileInfo.absoluteFilePath());
    }

protected:
    using ASTVisitor::visit;

    QMap<QByteArray, ClassSpecifierAST *> classMap;

    QByteArray id_cast(NameAST *name)
    {
        if (! name)
            return QByteArray();

        const Identifier *id = identifier(name->asSimpleName()->identifier_token);

        return QByteArray::fromRawData(id->chars(), id->size());
    }

    void visitMembers(Class *klass)
    {
        for (int i = 0; i < klass->memberCount(); ++i) {
            Symbol *member = klass->memberAt(i);
            if (! member->name())
                continue;

            const Identifier *id = member->name()->identifier();

            if (! id)
                continue;

            const QByteArray memberName = QByteArray::fromRawData(id->chars(), id->size());
            if (member->type()->asIntegerType() && memberName.endsWith("_token")) {
                *out << "    ast->" << memberName << " = " << memberName << ";" << Qt::endl;
            } else if (PointerType *ptrTy = member->type()->asPointerType()) {
                if (NamedType *namedTy = ptrTy->elementType()->asNamedType()) {
                    QByteArray typeName = namedTy->name()->identifier()->chars();

                    if (typeName.endsWith("ListAST")) {
                        *out << "    for (" << typeName << " *iter = " << memberName
                             << ", **ast_iter = &ast->" << memberName << ";" << Qt::endl
                             << "         iter; iter = iter->next, ast_iter = &(*ast_iter)->next)"
                             << Qt::endl
                             << "        *ast_iter = new (pool) " << typeName
                             << "((iter->value) ? iter->value->clone(pool) : nullptr);" << Qt::endl;
                    } else if (typeName.endsWith("AST")) {
                        *out << "    if (" << memberName << ")" << Qt::endl
                             << "        ast->" << memberName << " = " << memberName
                             << "->clone(pool);" << Qt::endl;
                    }
                }
            }
        }

        for (int i = 0; i < klass->baseClassCount(); ++i) {
            const QByteArray baseClassName = klass->baseClassAt(i)->identifier()->chars();

            if (ClassSpecifierAST *baseClassSpec = classMap.value(baseClassName, nullptr))
                visitMembers(baseClassSpec->symbol);
        }
    }

    bool checkMethod(Symbol *cloneMethod) const
    {
        Declaration *decl = cloneMethod->asDeclaration();
        if (! decl)
            return false;

        Function *funTy = decl->type()->asFunctionType();
        if (! funTy)
            return false;

        else if (funTy->isPureVirtual())
            return false;

        return true;
    }

    virtual bool visit(ClassSpecifierAST *ast)
    {
        Class *klass = ast->symbol;
        const QByteArray className = id_cast(ast->name);
        if (! className.endsWith("AST"))
            return false;

        const Identifier *clone_id = control()->identifier("clone");
        Symbol *cloneMethod = klass->find(clone_id);
        for (; cloneMethod; cloneMethod = cloneMethod->next()) {
            if (cloneMethod->identifier() != clone_id)
                continue;

            if (checkMethod(cloneMethod))
                break;
        }

        if (! cloneMethod)
            return true;

        classMap.insert(className, ast);

        *out << className.constData() << " *" << className.constData() << "::"
             << "clone(MemoryPool *pool) const" << Qt::endl
             << "{" << Qt::endl
             << "    " << className.constData() << " *ast = new (pool) " << className.constData()
             << ";" << Qt::endl;

        visitMembers(klass);

        *out << "    return ast;" << Qt::endl << "}" << Qt::endl << Qt::endl;

        return false;
    }
};

class GenerateDumpers: protected ASTVisitor
{
    QTextStream out;

public:
    GenerateDumpers(QBuffer *buffer, TranslationUnit *unit)
        : ASTVisitor(unit), out(buffer)
    { }

    static void go(const QString &fileName, TranslationUnit *unit)
    {
        QBuffer buffer;
        buffer.open(openFlags);

        GenerateDumpers d(&buffer, unit);
        d.out << copyrightHeader << generatedHeader << Qt::endl;

        d.accept(unit->ast());

        closeBufferAndWriteIfChanged(buffer, fileName);
    }

protected:
    using ASTVisitor::visit;

    QMap<QByteArray, ClassSpecifierAST *> classMap;

    QByteArray id_cast(NameAST *name)
    {
        if (! name)
            return QByteArray();

        const Identifier *id = identifier(name->asSimpleName()->identifier_token);

        return QByteArray::fromRawData(id->chars(), id->size());
    }

    void visitMembers(Class *klass)
    {
        for (int i = 0; i < klass->memberCount(); ++i) {
            Symbol *member = klass->memberAt(i);
            if (! member->name())
                continue;

            const Identifier *id = member->name()->identifier();

            if (! id)
                continue;

            const QByteArray memberName = QByteArray::fromRawData(id->chars(), id->size());
            if (member->type()->asIntegerType() && memberName.endsWith("_token")) {
                out << "    if (ast->" << memberName << ")" << Qt::endl;
                out << "        terminal(ast->" << memberName << ", ast);" << Qt::endl;
            } else if (PointerType *ptrTy = member->type()->asPointerType()) {
                if (NamedType *namedTy = ptrTy->elementType()->asNamedType()) {
                    QByteArray typeName = namedTy->name()->identifier()->chars();

                    if (typeName.endsWith("ListAST")) {
                        out << "    for (" << typeName << " *iter = ast->" << memberName
                            << "; iter; iter = iter->next)" << Qt::endl
                            << "        nonterminal(iter->value);" << Qt::endl;
                    } else if (typeName.endsWith("AST")) {
                        out << "    nonterminal(ast->" << memberName << ");" << Qt::endl;
                    }
                }
            }
        }

        for (int i = 0; i < klass->baseClassCount(); ++i) {
            const QByteArray baseClassName = klass->baseClassAt(i)->identifier()->chars();

            if (ClassSpecifierAST *baseClassSpec = classMap.value(baseClassName, nullptr))
                visitMembers(baseClassSpec->symbol);
        }
    }

    bool checkMethod(Symbol *cloneMethod) const
    {
        Declaration *decl = cloneMethod->asDeclaration();
        if (! decl)
            return false;

        Function *funTy = decl->type()->asFunctionType();
        if (! funTy)
            return false;

        else if (funTy->isPureVirtual())
            return false;

        return true;
    }

    virtual bool visit(ClassSpecifierAST *ast)
    {
        Class *klass = ast->symbol;
        const QByteArray className = id_cast(ast->name);
        if (! className.endsWith("AST"))
            return false;

        const Identifier *clone_id = control()->identifier("clone");
        Symbol *cloneMethod = klass->find(clone_id);
        for (; cloneMethod; cloneMethod = cloneMethod->next()) {
            if (cloneMethod->identifier() != clone_id)
                continue;

            if (checkMethod(cloneMethod))
                break;
        }

        if (! cloneMethod)
            return true;

        classMap.insert(className, ast);

        out << "virtual bool visit(" << className.constData() << " *ast)" << Qt::endl
            << "{" << Qt::endl;

        visitMembers(klass);

        out << "    return false;" << Qt::endl << "}" << Qt::endl << Qt::endl;

        return false;
    }
};

class RemoveCastMethods: protected ASTVisitor
{
public:
    RemoveCastMethods(Document::Ptr doc, QTextDocument *document)
        : ASTVisitor(doc->translationUnit()), document(document) {}

    QList<QTextCursor> operator()(AST *ast)
    {
        _cursors.clear();
        accept(ast);
        return _cursors;
    }

protected:
    virtual bool visit(FunctionDefinitionAST *ast)
    {
        Function *fun = ast->symbol;
        const QString functionName = oo.prettyName(fun->name());

        if (functionName.length() > 3 && functionName.startsWith(QLatin1String("as"))
            && functionName.at(2).isUpper()) {

            QTextCursor tc = createCursor(translationUnit(), ast, document);

            //qDebug() << qPrintable(tc.selectedText());
            _cursors.append(tc);
        }

        return true;
    }

private:
    QTextDocument *document;
    QList<QTextCursor> _cursors;
    Overview oo;
};

static QStringList collectFieldNames(ClassSpecifierAST *classAST, bool onlyTokensAndASTNodes)
{
    QStringList fields;
    Overview oo;
    Class *clazz = classAST->symbol;
    for (int i = 0; i < clazz->memberCount(); ++i) {
        Symbol *s = clazz->memberAt(i);
        if (Declaration *decl = s->asDeclaration()) {
            const QString declName = oo.prettyName(decl->name());
            const FullySpecifiedType ty = decl->type();
            if (const PointerType *ptrTy = ty->asPointerType()) {
                if (onlyTokensAndASTNodes) {
                    if (const NamedType *namedTy = ptrTy->elementType()->asNamedType()) {
                        if (oo.prettyName(namedTy->name()).endsWith(QLatin1String("AST")))
                            fields.append(declName);
                    }
                } else {
                    fields.append(declName);
                }
            } else if (ty->asIntegerType()) {
                fields.append(declName);
            }
        }
    }
    return fields;
}

bool checkGenerated(const QTextCursor &cursor, int *doxyStart)
{
    BackwardsScanner tokens(cursor, LanguageFeatures::defaultFeatures(), 10, QString(), false);
    Token prevToken = tokens.LA(1);
    if (prevToken.kind() != T_DOXY_COMMENT && prevToken.kind() != T_CPP_DOXY_COMMENT)
        return false;

    *doxyStart = tokens.startPosition() + prevToken.utf16charsBegin();

    return tokens.text(tokens.startToken() - 1).contains(QLatin1String("\\generated"));
}

struct GenInfo
{
    ClassSpecifierAST *classAST = nullptr;
    int start = 0;
    int end = 0;
    bool firstToken = false;
    bool lastToken = false;
    bool remove = false;
};

void generateFirstToken(QTextStream &os, const QString &className, const QStringList &fields)
{
    os << "int " << className << "::firstToken() const" << Qt::endl << "{" << Qt::endl;

    for (const QString &field : fields) {
        os << "    if (" << field << ")" << Qt::endl;

        if (field.endsWith(QLatin1String("_token"))) {
            os << "        return " << field << ";" << Qt::endl;
        } else {
            os << "        if (int candidate = " << field << "->firstToken())" << Qt::endl;
            os << "            return candidate;" << Qt::endl;
        }
    }

    os << "    return 0;" << Qt::endl;
    os << "}" << Qt::endl << Qt::endl;
}

void generateLastToken(QTextStream &os, const QString &className, const QStringList &fields)
{
    os << "int " << className << "::lastToken() const" << Qt::endl << "{" << Qt::endl;

    for (int i = fields.size() - 1; i >= 0; --i) {
        const QString field = fields.at(i);

        os << "    if (" << field << ")" << Qt::endl;

        if (field.endsWith(QLatin1String("_token"))) {
            os << "        return " << field << " + 1;" << Qt::endl;
        } else {
            os << "        if (int candidate = " << field << "->lastToken())" << Qt::endl;
            os << "            return candidate;" << Qt::endl;
        }
    }

    os << "    return 1;" << Qt::endl;
    os << "}" << Qt::endl << Qt::endl;
}

void generateAST_cpp(const Snapshot &snapshot, const QDir &cplusplusDir)
{
    typedef QMap<QString, ClassSpecifierAST *> StringClassSpecifierASTMap;
    typedef StringClassSpecifierASTMap::ConstIterator StringClassSpecifierASTMapConstIt;

    QFileInfo fileAST_cpp(cplusplusDir, QLatin1String("AST.cpp"));
    Q_ASSERT(fileAST_cpp.exists());

    const QString fileName = fileAST_cpp.absoluteFilePath();

    QFile file(fileName);
    if (! file.open(QFile::ReadOnly)) {
        std::cerr << "Cannot open " << fileName.toLatin1().data() << std::endl;
        return;
    }

    const QByteArray source = file.readAll();
    file.close();

    QTextDocument cpp_document;
    cpp_document.setPlainText(QString::fromUtf8(source));

    Document::Ptr AST_cpp_document = snapshot.preprocessedDocument(source, Utils::FilePath::fromString(fileName));
    AST_cpp_document->check();

    Overview oo;
    StringClassSpecifierASTMap classesNeedingFirstToken;
    StringClassSpecifierASTMap classesNeedingLastToken;

    // find all classes with method declarations for firstToken/lastToken
    for (ClassSpecifierAST *classAST : std::as_const(astNodes.deriveds)) {
        const QString className = oo.prettyName(classAST->symbol->name());
        if (className.isEmpty())
            continue;

        for (DeclarationListAST *declIter = classAST->member_specifier_list; declIter; declIter = declIter->next) {
            if (SimpleDeclarationAST *decl = declIter->value->asSimpleDeclaration()) {
                if (decl->symbols && decl->symbols->value) {
                    if (decl->symbols->next)
                        std::cerr << "Found simple declaration with multiple symbols in " << className.toLatin1().data() << std::endl;

                    Symbol *s = decl->symbols->value;
                    const QString funName = oo.prettyName(s->name());
                    if (funName == QLatin1String("firstToken")) {
                        // found it:
                        classesNeedingFirstToken.insert(className, classAST);
                    } else if (funName == QLatin1String("lastToken")) {
                        // found it:
                        classesNeedingLastToken.insert(className, classAST);
                    }
                }
            }
        }
    }

    QList<GenInfo> todo;

    TranslationUnitAST *xUnit = AST_cpp_document->translationUnit()->ast()->asTranslationUnit();
    for (DeclarationListAST *iter = xUnit->declaration_list; iter; iter = iter->next) {
        if (FunctionDefinitionAST *funDef = iter->value->asFunctionDefinition()) {
            if (const Name *name = funDef->symbol->name()) {
                if (const QualifiedNameId *qName = name->asQualifiedNameId()) {
                    const QString className = oo.prettyName(qName->base());
                    const QString methodName = oo.prettyName(qName->name());

                    QTextCursor cursor(&cpp_document);

                    int line = 0, column = 0;
                    AST_cpp_document->translationUnit()->getTokenPosition(funDef->firstToken(), &line, &column);
                    const int start = cpp_document.findBlockByNumber(line - 1).position() + column - 1;
                    cursor.setPosition(start);
                    int doxyStart = start;

                    const bool isGenerated = checkGenerated(cursor, &doxyStart);

                    AST_cpp_document->translationUnit()->getTokenEndPosition(funDef->lastToken() - 1, &line, &column);
                    int end = cpp_document.findBlockByNumber(line - 1).position() + column - 1;
                    while (cpp_document.characterAt(end).isSpace())
                        ++end;

                    if (methodName == QLatin1String("firstToken")) {
                        ClassSpecifierAST *classAST = classesNeedingFirstToken.value(className, nullptr);
                        GenInfo info;
                        info.end = end;
                        if (classAST) {
                            info.classAST = classAST;
                            info.firstToken = true;
                            info.start = start;
                            classesNeedingFirstToken.remove(className);
                        } else {
                            info.start = doxyStart;
                            info.remove = true;
                        }
                        if (isGenerated)
                            todo.append(info);
                    } else if (methodName == QLatin1String("lastToken")) {
                        ClassSpecifierAST *classAST = classesNeedingLastToken.value(className, nullptr);
                        GenInfo info;
                        info.end = end;
                        if (classAST) {
                            info.classAST = classAST;
                            info.start = start;
                            info.lastToken = true;
                            classesNeedingLastToken.remove(className);
                        } else {
                            info.start = doxyStart;
                            info.remove = true;
                        }
                        if (isGenerated)
                            todo.append(info);
                    }
                }

            }
        }
    }

    const int documentEnd = cpp_document.lastBlock().position() + cpp_document.lastBlock().length() - 1;

    Utils::ChangeSet changes;
    for (GenInfo info : std::as_const(todo)) {
        if (info.end > documentEnd)
            info.end = documentEnd;

        if (info.remove) {
            changes.remove(info.start, info.end);
            return;
        }

        Overview oo;

        const QString className = oo.prettyName(info.classAST->symbol->name());

        QString method;
        QTextStream os(&method);
        const QStringList fields = collectFieldNames(info.classAST, true);

        if (info.firstToken)
            generateFirstToken(os, className, fields);
        else if (info.lastToken)
            generateLastToken(os, className, fields);

        changes.replace(info.start, info.end, method);
    }

    QTextCursor tc(&cpp_document);
    changes.apply(&tc);

    QString newMethods;
    QTextStream os(&newMethods);
    const StringClassSpecifierASTMapConstIt cfend = classesNeedingFirstToken.constEnd();
    for (StringClassSpecifierASTMapConstIt it = classesNeedingFirstToken.constBegin(); it != cfend; ++it) {
        const QString &className = it.key();
        const QStringList fields = collectFieldNames(it.value(), true);
        os << "/** \\generated */" << Qt::endl;
        generateFirstToken(os, className, fields);
        if (ClassSpecifierAST *classAST = classesNeedingLastToken.value(className, nullptr)) {
            const QStringList fields = collectFieldNames(classAST, true);
            os << "/** \\generated */" << Qt::endl;
            generateLastToken(os, className, fields);
            classesNeedingLastToken.remove(className);
        }
    }
    const StringClassSpecifierASTMapConstIt clend = classesNeedingLastToken.constEnd();
    for (StringClassSpecifierASTMapConstIt it = classesNeedingLastToken.constBegin(); it != clend; ++it) {
        os << "/** \\generated */" << Qt::endl;
        generateLastToken(os, it.key(), collectFieldNames(it.value(), true));
    }
    tc.setPosition(documentEnd);
    tc.insertText(newMethods);

    writeIfChanged(cpp_document.toPlainText(), fileName);
}

void generateASTVisitor_H(const Snapshot &, const QDir &cplusplusDir,
                          const QList<QByteArray> &classes)
{
  QFileInfo fileASTVisitor_h(cplusplusDir, QLatin1String("ASTVisitor.h"));
  Q_ASSERT(fileASTVisitor_h.exists());

  QBuffer buffer;
  buffer.open(openFlags);
  QTextStream out(&buffer);
  out << copyrightHeader <<
"\n"
"#pragma once\n"
"\n"
"#include \"CPlusPlusForwardDeclarations.h\"\n"
"#include \"ASTfwd.h\"\n"
"\n"
"namespace CPlusPlus {\n"
"\n"
"class CPLUSPLUS_EXPORT ASTVisitor\n"
"{\n"
"    ASTVisitor(const ASTVisitor &other);\n"
"    void operator =(const ASTVisitor &other);\n"
"\n"
"public:\n"
"    ASTVisitor(TranslationUnit *unit);\n"
"    virtual ~ASTVisitor();\n"
"\n"
"    TranslationUnit *translationUnit() const;\n"
"    void setTranslationUnit(TranslationUnit *translationUnit);\n"
"\n"
"    Control *control() const;\n"
"    int tokenCount() const;\n"
"    const Token &tokenAt(int index) const;\n"
"    int tokenKind(int index) const;\n"
"    const char *spell(int index) const;\n"
"    const Identifier *identifier(int index) const;\n"
"    const Literal *literal(int index) const;\n"
"    const NumericLiteral *numericLiteral(int index) const;\n"
"    const StringLiteral *stringLiteral(int index) const;\n"
"\n"
"    void getPosition(int offset,\n"
"                     int *line,\n"
"                     int *column = nullptr,\n"
"                     const StringLiteral **fileName = nullptr) const;\n"
"\n"
"    void getTokenPosition(int index,\n"
"                          int *line,\n"
"                          int *column = nullptr,\n"
"                          const StringLiteral **fileName = nullptr) const;\n"
"\n"
"    void getTokenStartPosition(int index, int *line, int *column) const;\n"
"    void getTokenEndPosition(int index, int *line, int *column) const;\n"
"\n"
"    void accept(AST *ast);\n"
"\n"
"    template <typename Tptr>\n"
"    void accept(List<Tptr> *it)\n"
"    {\n"
"        for (; it; it = it->next)\n"
"            accept(it->value);\n"
"    }\n"
"\n"
"    virtual bool preVisit(AST *) { return true; }\n"
"    virtual void postVisit(AST *) {}\n";

  out << "\n";
  for (const QByteArray &klass : classes)
    out << "    virtual bool visit(" << klass << " *) { return true; }\n";

  out << "\n";
  for (const QByteArray &klass : classes)
    out << "    virtual void endVisit(" << klass << " *) {}\n";
  out << "\n";

  out <<
"private:\n"
"   TranslationUnit *_translationUnit;\n"
"};\n"
"\n"
"} // namespace CPlusPlus\n";

  closeBufferAndWriteIfChanged(buffer, fileASTVisitor_h.absoluteFilePath());
}

void generateASTMatcher_H(const Snapshot &, const QDir &cplusplusDir,
                          const QList<QByteArray> &classes)
{
  QFileInfo fileASTMatcher_h(cplusplusDir, QLatin1String("ASTMatcher.h"));
  Q_ASSERT(fileASTMatcher_h.exists());

  QBuffer buffer;
  buffer.open(openFlags);
  QTextStream out(&buffer);
  out << copyrightHeader <<
"\n"
"#pragma once\n"
"\n"
"#include \"ASTfwd.h\"\n"
"\n"
"namespace CPlusPlus {\n"
"\n"
"class CPLUSPLUS_EXPORT ASTMatcher\n"
"{\n"
"public:\n"
"    ASTMatcher();\n"
"    virtual ~ASTMatcher();\n"
"\n";

  for (const QByteArray &klass : classes) {
    out << "    virtual bool match(" << klass << " *node, " << klass << " *pattern);\n";
  }

  out <<
"};\n"
"\n"
"} // namespace CPlusPlus\n";

  closeBufferAndWriteIfChanged(buffer, fileASTMatcher_h.absoluteFilePath());
}

QStringList generateAST_H(const Snapshot &snapshot, const QDir &cplusplusDir, const QString &dumpersFile)
{
    QStringList astDerivedClasses;

    QFileInfo fileAST_h(cplusplusDir, QLatin1String("AST.h"));
    Q_ASSERT(fileAST_h.exists());

    const QString fileName = fileAST_h.absoluteFilePath();

    QFile file(fileName);
    if (! file.open(QFile::ReadOnly))
        return astDerivedClasses;

    const QByteArray source = file.readAll();
    file.close();

    QTextDocument document;
    document.setPlainText(QString::fromUtf8(source));

    AST_h_document  = snapshot.preprocessedDocument(source, Utils::FilePath::fromString(fileName));
    AST_h_document->check();

    FindASTNodes process(AST_h_document, &document);
    astNodes = process(AST_h_document->translationUnit()->ast());

    RemoveCastMethods removeCastMethods(AST_h_document, &document);

    QList<QTextCursor> baseCastMethodCursors = removeCastMethods(astNodes.base);
    QMap<ClassSpecifierAST *, QList<QTextCursor> > cursors;
    QMap<ClassSpecifierAST *, QString> replacementCastMethods;

    Overview oo;

    QStringList castMethods;
    for (ClassSpecifierAST *classAST : std::as_const(astNodes.deriveds)) {
        cursors[classAST] = removeCastMethods(classAST);
        const QString className = oo.prettyName(classAST->symbol->name());
        const QString methodName = QLatin1String("as") + className.mid(0, className.length() - 3);
        replacementCastMethods[classAST]
                = QString::fromLatin1("    virtual %1 *%2() { return this; }\n")
                    .arg(className, methodName);
        castMethods.append(
                QString::fromLatin1("    virtual %1 *%2() { return nullptr; }\n")
                    .arg(className, methodName));
        astDerivedClasses.append(className);
    }

    if (! baseCastMethodCursors.isEmpty()) {
        castMethods.sort();
        for (int i = 0; i < baseCastMethodCursors.length(); ++i) {
            baseCastMethodCursors[i].removeSelectedText();
        }

        baseCastMethodCursors.first().insertText(castMethods.join(QLatin1String("")));
    }

    for (int classIndex = 0; classIndex < astNodes.deriveds.size(); ++classIndex) {
        ClassSpecifierAST *classAST = astNodes.deriveds.at(classIndex);

        { // remove the cast methods.
            QList<QTextCursor> c = cursors.value(classAST);
            for (int i = 0; i < c.length(); ++i) {
                c[i].removeSelectedText();
            }
        }

        astNodes.endOfPublicClassSpecifiers[classIndex].insertText(
                replacementCastMethods.value(classAST));
    }

    writeIfChanged(document.toPlainText(), fileName);

    Accept0CG cg(cplusplusDir, AST_h_document->translationUnit());
    cg(AST_h_document->translationUnit()->ast());
    const QList<QByteArray> astClasses = cg.classes();

    Match0CG cg2(cplusplusDir, AST_h_document->translationUnit());
    cg2(AST_h_document->translationUnit()->ast());

    MatcherCPPCG cg3(cplusplusDir, AST_h_document->translationUnit());
    cg3(AST_h_document->translationUnit()->ast());

    CloneCPPCG cg4(cplusplusDir, AST_h_document->translationUnit());
    cg4(AST_h_document->translationUnit()->ast());

    generateAST_cpp(snapshot, cplusplusDir);

    generateASTVisitor_H(snapshot, cplusplusDir, astClasses);
    generateASTMatcher_H(snapshot, cplusplusDir, astClasses);

    if (!dumpersFile.isEmpty())
        GenerateDumpers::go(dumpersFile, AST_h_document->translationUnit());

    return astDerivedClasses;
}

class FindASTForwards: protected ASTVisitor
{
public:
    FindASTForwards(Document::Ptr doc, QTextDocument *document)
        : ASTVisitor(doc->translationUnit()), document(document)
    {}

    QList<QTextCursor> operator()(AST *ast)
    {
        accept(ast);
        return _cursors;
    }

protected:
    bool visit(SimpleDeclarationAST *ast)
    {
        if (! ast->decl_specifier_list)
            return false;

        if (ElaboratedTypeSpecifierAST *e = ast->decl_specifier_list->value->asElaboratedTypeSpecifier()) {
            if (tokenKind(e->classkey_token) == T_CLASS && !ast->declarator_list) {
                QString className = oo.prettyName(e->name->name);

                if (className.length() > 3 && className.endsWith(QLatin1String("AST"))) {
                    QTextCursor tc = createCursor(translationUnit(), ast, document);
                    _cursors.append(tc);
                }
            }
        }

        return true;
    }

private:
    QTextDocument *document;
    QList<QTextCursor> _cursors;
    Overview oo;
};

void generateASTFwd_h(const Snapshot &snapshot, const QDir &cplusplusDir, const QStringList &astDerivedClasses)
{
    QFileInfo fileASTFwd_h(cplusplusDir, QLatin1String("ASTfwd.h"));
    Q_ASSERT(fileASTFwd_h.exists());

    const QString fileName = fileASTFwd_h.absoluteFilePath();

    QFile file(fileName);
    if (! file.open(QFile::ReadOnly))
        return;

    const QByteArray source = file.readAll();
    file.close();

    QTextDocument document;
    document.setPlainText(QString::fromUtf8(source));
    Document::Ptr doc = snapshot.preprocessedDocument(source, Utils::FilePath::fromString(fileName));
    doc->check();

    FindASTForwards process(doc, &document);
    QList<QTextCursor> cursors = process(doc->translationUnit()->ast());

    for (int i = 0; i < cursors.length(); ++i)
        cursors[i].removeSelectedText();

    QString replacement;
    for (const QString &astDerivedClass : astDerivedClasses)
        replacement += QString("class %1;\n").arg(astDerivedClass);

    cursors.first().insertText(replacement);

    writeIfChanged(document.toPlainText(), fileName);
}

void generateASTPatternBuilder_h(const QDir &cplusplusDir)
{
    typedef QPair<QString, QString> StringPair;

    QFileInfo fileInfo(cplusplusDir, QLatin1String("ASTPatternBuilder.h"));

    Overview oo;
    QBuffer buffer;
    buffer.open(openFlags);
    QTextStream out(&buffer);

    out << copyrightHeader << generatedHeader << "#pragma once" << Qt::endl
        << Qt::endl
        << "#include \"CPlusPlusForwardDeclarations.h\"" << Qt::endl
        << "#include \"AST.h\"" << Qt::endl
        << "#include \"MemoryPool.h\"" << Qt::endl
        << Qt::endl
        << "namespace CPlusPlus {" << Qt::endl
        << Qt::endl
        << "class CPLUSPLUS_EXPORT ASTPatternBuilder" << Qt::endl
        << "{" << Qt::endl
        << "    MemoryPool pool;" << Qt::endl
        << Qt::endl
        << "public:" << Qt::endl
        << "    ASTPatternBuilder() {}" << Qt::endl
        << Qt::endl
        << "    void reset() { pool.reset(); }" << Qt::endl
        << Qt::endl;

    Control *control = AST_h_document->control();
    QSet<QString> classesSet;

    for (ClassSpecifierAST *classNode : std::as_const(astNodes.deriveds)) {
        Class *klass = classNode->symbol;

        const Identifier *match0_id = control->identifier("match0");
        Symbol *match0Method = klass->find(match0_id);
        for (; match0Method; match0Method = match0Method->next()) {
            if (match0Method->identifier() != match0_id)
                continue;
            else break;
        }

        if (! match0Method)
            continue;

        const QString className = oo.prettyName(klass->name());

        if (! className.endsWith(QLatin1String("AST")))
            continue;

        const QString methodName = className.left(className.length() - 3);

        out
                << "    " << className << " *" << methodName << "(";

        QList<StringPair> args;

        bool first = true;
        for (int index = 0; index < klass->memberCount(); ++index) {
            Declaration *member = klass->memberAt(index)->asDeclaration();
            if (! member)
                continue;

            PointerType *ptrTy = member->type()->asPointerType();
            if (! ptrTy)
                continue;

            const QString tyName = oo.prettyType(ptrTy->elementType());
            if (tyName.endsWith(QLatin1String("ListAST")))
                classesSet.insert(tyName);
            if (tyName.endsWith(QLatin1String("AST"))) {
                if (! first)
                    out << ", ";

                const QString memberName = oo.prettyName(member->name());

                out << tyName << " *" << memberName << " = nullptr";
                args.append(qMakePair(tyName, memberName));
                first = false;
            }
        }

        out << ")" << Qt::endl
            << "    {" << Qt::endl
            << "        " << className << " *ast = new (&pool) " << className << ';' << Qt::endl;

        for (const StringPair &p : std::as_const(args))
            out << "        ast->" << p.second << " = " << p.second << ';' << Qt::endl;

        out << "        return ast;" << Qt::endl << "    }" << Qt::endl << Qt::endl;
    }

    QStringList classesList = Utils::toList(classesSet);
    Utils::sort(classesList);
    for (const QString &className : std::as_const(classesList)) {
        const QString methodName = className.left(className.length() - 3);
        const QString elementName = className.left(className.length() - 7) + QLatin1String("AST");
        out << "    " << className << " *" << methodName << "(" << elementName << " *value, "
            << className << " *next = nullptr)" << Qt::endl
            << "    {" << Qt::endl
            << "        " << className << " *list = new (&pool) " << className << ";" << Qt::endl
            << "        list->next = next;" << Qt::endl
            << "        list->value = value;" << Qt::endl
            << "        return list;" << Qt::endl
            << "    }" << Qt::endl
            << Qt::endl;
    }

    out << "};" << Qt::endl << Qt::endl << "} // end of namespace CPlusPlus" << Qt::endl;

    closeBufferAndWriteIfChanged(buffer, fileInfo.absoluteFilePath());
}

void printUsage()
{
    const QByteArray executable = QFileInfo(QCoreApplication::arguments().first()).fileName().toLatin1();
    std::cout << "Usage: " << executable.constData() << "\n"
              << "       " << executable.constData() << " <frontend-dir> <dumpers-file>"
              << "\n\n"
              << "Generate appropriate header and source files of the C++ frontend accordingly\n"
              << "to AST.h and print the paths of the written files. Run this tool after\n"
              << "modifying AST.h."
              << "\n\n";
    const QString defaultPathCppFrontend
        = QFileInfo(QLatin1String(PATH_CPP_FRONTEND)).canonicalFilePath();
    const QString defaultPathDumpersFile
        = QFileInfo(QLatin1String(PATH_DUMPERS_FILE)).canonicalFilePath();
    std::cout << "Default values:" << "\n"
              << "   frontend-dir: " << qPrintable(defaultPathCppFrontend) << "\n"
              << "   dumpers-file: " << qPrintable(defaultPathDumpersFile) << "\n";
}

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QStringList args = app.arguments();
    args.removeFirst();

    QString pathCppFrontend = QLatin1String(PATH_CPP_FRONTEND);
    QString pathDumpersFile = QLatin1String(PATH_DUMPERS_FILE);

    const bool helpRequested = args.contains(QLatin1String("-h"))
        || args.contains(QLatin1String("-help"));
    if (args.count() == 1 || args.count() >= 3 || helpRequested) {
        printUsage();
        return helpRequested ? EXIT_SUCCESS : EXIT_FAILURE;
    } else if (args.count() == 2) {
        pathCppFrontend = args.at(0);
        pathDumpersFile = args.at(1);
    }

    QDir cplusplusDir(pathCppFrontend);
    if (!QFileInfo::exists(pathCppFrontend)) {
        std::cerr << "Error: Directory \"" << qPrintable(cplusplusDir.absolutePath())
                  << "\" does not exist." << std::endl;
        return EXIT_FAILURE;
    }
    if (!QFileInfo(cplusplusDir, QLatin1String("AST.h")).exists()) {
        std::cerr << "Error: Cannot find AST.h in \"" << qPrintable(cplusplusDir.absolutePath())
                  << "\"." << std::endl;
        return EXIT_FAILURE;
    }
    if (!QFileInfo::exists(pathDumpersFile)) {
        std::cerr << "Error: File \"" << qPrintable(pathDumpersFile)
                  << "\" does not exist." << std::endl;
        return EXIT_FAILURE;
    }

    Snapshot snapshot;
    QStringList astDerivedClasses = generateAST_H(snapshot, cplusplusDir, pathDumpersFile);
    astDerivedClasses.sort();
    generateASTFwd_h(snapshot, cplusplusDir, astDerivedClasses);
    generateASTPatternBuilder_h(cplusplusDir);

    return EXIT_SUCCESS;
}
